﻿using System;
using System.Collections.Generic;
using System.Linq;
using AutoMapper;
using CollegeSim.Domain.DTO;
using CollegeSim.Domain.Entities;
using CollegeSim.Domain.Interfaces.Repositories;
using CollegeSim.Domain.Interfaces.Services;

namespace CollegeSim.Domain.Services
{
    // Author: Ryan Redburn
    // Project: CollegeSim
    // Date: 2/8/2014

    #region [ Updates ]

    // Author: Ryan Redburn
    // Date: 5/6/2014
    // Revisions: Changed service to reflect version 2.0 changes.

    // Author: Ryan Redburn
    // Date: 5/12/2014
    // Revisions: Altered Update() to take an identifying ID value. Added exception handling to FindByID(). Fixed some spelling and added a few comments.

    // Author: Ryan Redburn
    // Date: 5/18/2014
    // Revisions: Removed Automapper mapping from CourseDTO -> Course due to exception encapsulation problems. Wrote MapDTO() which now fulfills that functionality.

    #endregion

    public class CourseService : ICourseService
    {
        #region [ Fields ]

        private readonly ICourseRepository _courseRepository;

        #endregion

        #region [ Constructors ]

        // Requires an instance of ICourseRepository
        public CourseService(ICourseRepository courseRepository)
        {
            _courseRepository = courseRepository;
        }

        #endregion

        #region [ Methods ]

        /// <summary>
        /// Returns a list of all Courses.
        /// </summary>
        /// <returns>List of CourseDto.</returns>
        public List<CourseDTO> GetAll()
        {
            // If invalid data is received from persistence an exception is thrown to indicate thusly
            try
            {
                var courses = _courseRepository.FindAll().Select(MapDTO).ToList(); // Validate data
                return Mapper.Map<List<CourseDTO>>(courses);
            }
            catch (ArgumentNullException)
            {
                throw new InvalidOperationException("Invalid data may have been received from persistence.");
            }
            catch (ArgumentOutOfRangeException)
            {
                throw new InvalidOperationException("Invalid data may have been received from persistence.");
            }
        }

        /// <summary>
        /// Returns a course with a matching ID.
        /// </summary>
        /// <param name="courseID">Integer course ID.</param>
        /// <returns>CourseDto</returns>
        public CourseDTO GetByID(int courseID)
        {
            // Throws exception for invaild ID value
            if (courseID < 1)
                throw new ArgumentOutOfRangeException("courseID",
                    "courseID does not accept an integer less than one as an argument.");

            // If invalid data is received from persistence an exception is thrown to indicate thusly
            try
            {
                // Validate and return course
                var course = MapDTO(_courseRepository.FindByID(courseID));
                return Mapper.Map<CourseDTO>(course);
            }
            catch (ArgumentNullException)
            {
                throw new InvalidOperationException("Invalid data may have been received from persistence.");
            }
            catch (ArgumentOutOfRangeException)
            {
                throw new InvalidOperationException("Invalid data may have been received from persistence.");
            }
        }

        /// <summary>
        /// Validates and commits a course for persistence.
        /// </summary>
        /// <param name="newCourse">Course to save.</param>
        public void Commit(CourseDTO newCourse)
        {
            try
            {
                // Validate and submit course to persistence
                var course = MapDTO(newCourse);
                _courseRepository.Insert(Mapper.Map<CourseDTO>(course));
            }
            catch (ArgumentNullException x1)
            {
                throw new ArgumentException(x1.Message);
            }
            catch (ArgumentOutOfRangeException x2)
            {
                throw new ArgumentException(x2.Message);
            }
        }

        /// <summary>
        /// Validates and submits a course for updating.
        /// </summary>
        /// <param name="courseID">ID of course to update.</param>
        /// <param name="courseUpdate">Existing course to update</param>
        public void Update(int courseID, CourseDTO courseUpdate)
        {
            // Throw exception if no course matching the given is found
            if (_courseRepository.FindByID(courseID) == null)
                throw new InvalidOperationException("No course matching the given exists.");

            try
            {
                // Validate and submit course to persistence
                var course = MapDTO(courseUpdate);
                _courseRepository.Update(Mapper.Map<CourseDTO>(course));
            }
            catch (ArgumentNullException x1)
            {
                throw new ArgumentException(x1.Message);
            }
            catch (ArgumentOutOfRangeException x2)
            {
                throw new ArgumentException(x2.Message);
            }
        }

        /// <summary>
        /// Validates and submits a course for deletion.
        /// </summary>
        /// <param name="courseDel">Course to be deleted.</param>
        public void Delete(CourseDTO courseDel)
        {
            // Throw exception if no course matching the given is found
            if (_courseRepository.FindByID(courseDel.CourseID) == null)
                throw new InvalidOperationException("No course matching the given exists.");
            try
            {
                // Submit course to persistence for deletion
                var course = MapDTO(courseDel);
                _courseRepository.Delete(Mapper.Map<CourseDTO>(course));
            }
            catch (ArgumentNullException x1)
            {
                throw new ArgumentException(x1.Message);
            }
            catch (ArgumentOutOfRangeException x2)
            {
                throw new ArgumentException(x2.Message);
            }
        }

        #endregion

        #region [ Internal Methods ]

        /// <summary>
        /// Maps a CourseDTO to a domain course.
        /// </summary>
        /// <param name="dto">The CourseDTO to map.</param>
        /// <returns>Mapped domain course.</returns>
        private Course MapDTO(CourseDTO dto)
        {
            var mappedCourse = new Course
            {
                CourseID = dto.CourseID,
                Subject = dto.Subject,
                Number = dto.Number,
                Title = dto.Title,
                CreditHours = dto.CreditHours,
                ContactHours = dto.ContactHours,
                Online = dto.Online,
                Hybrid = dto.Hybrid
            };

            return mappedCourse;
        }

        #endregion
    }
}
